// main.cc
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <vector>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"
using namespace std;

const static int pnum = 5; // 子进程的个数

class CommunicationInfo
{
public:
    CommunicationInfo(int writefd, int pid)
        : _writefd(writefd),
          _pid(pid)
    {
        // 格式为process-0[writefd:pid]
        _name += "process-" + to_string(_cnt) + "[" + to_string(_writefd) + ":" + to_string(_pid) + "]";
        _cnt++;
    }

    string processname() const
    {
        return _name;
    }

public:
    int _writefd; // 父进程的写描述符
    int _pid;     // 子进程的pid
    string _name; // 子进程的名字
private:
    static int _cnt; // 表示第几个进程
};
int CommunicationInfo::_cnt = 0;


void WaitCommand()
{
    while (true)
    {
        Task task;
        // 子进程在这读取任务
        // 1.读取任务
        int command = 0;
        int n = read(0, &command, sizeof(command)); // 之前重定向的原因就是为了在这能够更好的统一读取
        if (n == sizeof(int))
        {
            // 读取成功
            // 2.执行任务
            task.Execute(command);
            cerr<<getpid()<<":任务执行完成"<<endl;
        }
        else if (n == 0)
        {
            cerr << getpid() << ":父进程让我退出，我就退出了" << endl;
            // 写端关闭了，子进程那么也可以关闭读端了
           sleep(1);//更好的查看子进程退出时的情况
            break;
        }
        else
        {
            cerr<<getpid()<<":读取错误"<<endl;
            // 读取错误
            break;
        }
    }
}

void CreateProcess(vector<CommunicationInfo> *communicationinfo)
{
    vector<int> fds;//当多次循环时，父进程的write描述符会越来越多，这导致创建的子进程也会复制到自己的描述符表中，最后的结果就是子进程无法读到0
    //循环创建多个子进程
    for (int i = 0; i < pnum; i++)
    {
        // 1.先创建匿名管道
        int fd[2] = {0};
        int n = pipe(fd);
        assert(n == 0);
        // 防止编译时报警告
        (void)n;
        // 2.创建多个子进程
        int pid = fork();
        if (pid == 0)
        {
            //先释放父进程的写描述符
            for(auto e:fds)
             {
                 close(e);
            }

            // 子进程
            // 2.1将子进程不需要的描述符关闭
            close(fd[1]);
            // 2.2将子进程通信的管道重定向到0号标准输入中,这是为了更方便的统一使用0号描述符进行读
            dup2(fd[0], 0);
            // 2.3获取命令，执行任务
            WaitCommand(); // 对于前面没有进行重定向，也可以直接通过传递参数的方式进行通信
            // 2.4父进程关闭写端，子进程则关闭读端，然后退出
            close(fd[0]);
            exit(0);
        }
       

        // 3.父进程
        // 3.1关闭子进程读的描述符
        close(fd[0]);
        // 3.2父进程将自己写端的描述符和子进程的pid保存起来，方便后面通信，用先描述再组织的方式
        communicationinfo->push_back(CommunicationInfo(fd[1], pid));

        fds.push_back(fd[1]);

         //--debug--查看我们的文件描述符的值->通过重定向，正常打印用cerr即可
        cout<<i+1<<":parent_fd->"<<fd[1]<<",child_fd->"<<fd[0]<<endl;
    }
}

int ShowTask()
{
    fprintf(stderr,"******************************************\n");
    fprintf(stderr,"*******  0.MySQL       1.Reuqest   *******\n");
    fprintf(stderr,"*******  2.Link        3.exit      *******\n");
    cerr<<"请选择:";
    int command = 0;
    cin >> command;
    return command;
}

void CtrlProcess(const vector<CommunicationInfo> &communicationinfo)
{

    int cnt = 0;
    while (true)
    {
        //1.选择任务
        int command = ShowTask();
        //用户想要退出
        if(command==3)
        {
            break;
        }
        if (command < 0 || command > 2)
        {
           continue;
        }
        //2.选择子进程
        int index=cnt++;
        cnt%=communicationinfo.size();
        cerr<<"选择了子进程:"<<communicationinfo[index]._name<<"|"<<"处理任务:"<<(command==0?"MySQL":command==1?"Request":"Link")<<endl;

        //2.对指定子进程发送任务
        write(communicationinfo[index]._writefd, &command, sizeof(command));
        sleep(1);
    }
}


void WaitAllProcess(const vector<CommunicationInfo>& communicationinfo)//处理所有的子进程的退出
{
    // //可以不使用容器来保存父进程的写描述符的写法，也是通过最后一个子进程先退出的原理
    // for(int i=0;i<communicationinfo.size();i++)
    // {
    //     close(communicationinfo[i]._writefd);
    // }


    // for(int i=0;i<communicationinfo.size();i++)
    // {
    //     waitpid(communicationinfo[i]._pid,nullptr,0);
    //     cerr<<communicationinfo[i]._name<<"已退出"<<endl;
    // }


    // //使用这种写法就可以不使用容器保存父进程的写描述符
    // for(int i=communicationinfo.size()-1;i>=0;i--)
    // {
    //     close(communicationinfo[i]._writefd);
    //     waitpid(communicationinfo[i]._pid,nullptr,0);
    // }



    //通过关闭写端的描述符，读端就会接收到0返回值，即可使子进程退出--需要创建子进程时使用容器来保存父进程的写描述符的写法
    for(int i=0;i<communicationinfo.size();i++)
    {
        close(communicationinfo[i]._writefd);
        waitpid(communicationinfo[i]._pid,nullptr,0);
        cerr<<communicationinfo[i]._name<<"已退出"<<endl;
    }

}

// 父进程负责写，子进程负责读
int main()
{
    vector<CommunicationInfo> communicationinfo; // 存储通信有关的父进程写端描述符和子进程pid
    // 1.创建子进程（多进程）
    CreateProcess(&communicationinfo);
    // 2.显示让用户选择执行哪个任务,按照进程的顺序执行
    CtrlProcess(communicationinfo);
    //3.回收子进程，执行退出   
    WaitAllProcess(communicationinfo);
    return 0;
}